package main

import (
	"errors"
	"fmt"

	"github.com/hyperledger/fabric-protos-go/common"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	mspClient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	mspProvider "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/policydsl"
)

type FabricClient struct {
	Sdk             *fabsdk.FabricSDK
	Orderer         *OrdererInfo
	RetryOpts       retry.Opts
	SignaturePolicy *common.SignaturePolicyEnvelope
}

func NewFabricClient(configPath string, orderer *OrdererInfo, policyMember string) *FabricClient {
	fabricClient := FabricClient{}
	fabricClient.initSDK(configPath)

	fabricClient.RetryOpts = retry.Opts{
		Attempts:       2,
		InitialBackoff: 1,
		MaxBackoff:     10,
		BackoffFactor:  2,
		RetryableCodes: retry.DefaultResMgmtOpts.RetryableCodes,
	}

	fabricClient.Orderer = orderer

	fabricClient.SignaturePolicy = policydsl.SignedByAnyMember([]string{policyMember})

	return &fabricClient
}

func (c *FabricClient) genPolicy(p string) (*common.SignaturePolicyEnvelope, error) {
	return policydsl.FromString(p)
}

func (c *FabricClient) initSDK(configPath string) {
	sdk, err := fabsdk.New(config.FromFile(configPath))
	if err != nil {
		fmt.Printf("failed to create Sdk: %v\n", err)
	}
	c.Sdk = sdk
}

func (c *FabricClient) FillCCVersionAndSequence(peer *PeerInfo, channelId string, ccInfo *ChaincodeInfo) error {
	if ccInfo == nil {
		return errors.New("ccInfo is nil")
	}
	ccContext := c.Sdk.Context(fabsdk.WithUser("Admin"), fabsdk.WithOrg(peer.OrgInfo.Name))
	resMgmt, err := resmgmt.New(ccContext)
	if err != nil {
		return err
	}
	ccDefs, err := c.queryCommittedCC(channelId, ccInfo.Label, resMgmt, []string{peer.Endpoint})
	if err != nil {
		return err
	}

	for _, ccDef := range ccDefs {
		if ccDef.Name == ccInfo.Label {
			ccInfo.Version = ccDef.Version
			ccInfo.Sequence = ccDef.Sequence
			return nil
		}
	}
	ccInfo.Version = "1.0"
	ccInfo.Sequence = 1
	return nil
}

func (c *FabricClient) JoinChannel(channelId string, peer *PeerInfo) error {
	adminOrgContext := c.Sdk.Context(
		fabsdk.WithUser(peer.OrgInfo.Admin),
		fabsdk.WithOrg(peer.OrgInfo.Name))

	orgResMgmt, err := resmgmt.New(adminOrgContext)
	if err != nil {
		return err
	}

	if err = orgResMgmt.JoinChannel(
		channelId,
		resmgmt.WithRetry(c.RetryOpts),
		resmgmt.WithTargetEndpoints(peer.Endpoint),
		resmgmt.WithOrdererEndpoint(c.Orderer.Endpoint)); err != nil {
		return err
	}
	fmt.Println(fmt.Sprintf("%s join channel %s successfully", peer.Endpoint, channelId))
	return nil
}

func (c *FabricClient) installCC(ccInfo *ChaincodeInfo, orgResMgmt *resmgmt.Client, peerEndpoint string) error {
	if _, err := orgResMgmt.LifecycleInstallCC(
		resmgmt.LifecycleInstallCCRequest{Label: ccInfo.Label, Package: ccInfo.PkgBytes},
		resmgmt.WithRetry(c.RetryOpts),
		resmgmt.WithTargetEndpoints(peerEndpoint)); err != nil {
		return err
	}
	return nil
}

func (c *FabricClient) getInstalledCCPackage(packageID string, orgResMgmt *resmgmt.Client, peerEndpoint string) error {
	if _, err := orgResMgmt.LifecycleGetInstalledCCPackage(
		packageID,
		resmgmt.WithRetry(c.RetryOpts),
		resmgmt.WithTargetEndpoints(peerEndpoint)); err != nil {
		return err
	}
	return nil
}

func (c *FabricClient) queryInstalled(orgResMgmt *resmgmt.Client, peerEndpoint string) ([]resmgmt.LifecycleInstalledCC, error) {
	if resp, err := orgResMgmt.LifecycleQueryInstalledCC(
		resmgmt.WithRetry(c.RetryOpts),
		resmgmt.WithTargetEndpoints(peerEndpoint)); err != nil {
		return nil, err
	} else {
		return resp, nil
	}
}

func (c *FabricClient) approveCC(channelId string, ccInfo *ChaincodeInfo, orgResMgmt *resmgmt.Client, peerEndPoint, ordererEndpoint string) error {
	approveCCReq := resmgmt.LifecycleApproveCCRequest{
		Name:              ccInfo.Label,
		Version:           ccInfo.Version,
		PackageID:         ccInfo.PackageId,
		Sequence:          ccInfo.Sequence,
		EndorsementPlugin: "escc",
		ValidationPlugin:  "vscc",
		SignaturePolicy:   c.SignaturePolicy,
		InitRequired:      true,
	}

	if _, err := orgResMgmt.LifecycleApproveCC(channelId, approveCCReq,
		resmgmt.WithTargetEndpoints(peerEndPoint),
		resmgmt.WithOrdererEndpoint(ordererEndpoint),
		resmgmt.WithRetry(c.RetryOpts)); err != nil {
		return err
	}
	return nil
}

func (c *FabricClient) queryApprovedCC(channelId string, ccInfo *ChaincodeInfo, orgResMgmt *resmgmt.Client, peerEndpoint string) error {
	if _, err := orgResMgmt.LifecycleQueryApprovedCC(
		channelId,
		resmgmt.LifecycleQueryApprovedCCRequest{Name: ccInfo.Label, Sequence: ccInfo.Sequence},
		resmgmt.WithTargetEndpoints(peerEndpoint),
		resmgmt.WithRetry(c.RetryOpts)); err != nil {
		return err
	}
	return nil
}

func (c *FabricClient) checkCCCommitReadiness(channelId string, ccInfo *ChaincodeInfo, orgResMgmt *resmgmt.Client, peerEndpoint string) error {
	req := resmgmt.LifecycleCheckCCCommitReadinessRequest{
		Name:              ccInfo.Label,
		Version:           ccInfo.Version,
		EndorsementPlugin: "escc",
		ValidationPlugin:  "vscc",
		Sequence:          ccInfo.Sequence,
		SignaturePolicy:   c.SignaturePolicy,
		InitRequired:      true,
	}
	if resp, err := orgResMgmt.LifecycleCheckCCCommitReadiness(channelId, req,
		resmgmt.WithTargetEndpoints(peerEndpoint),
		resmgmt.WithRetry(c.RetryOpts)); err != nil {
		return err
	} else {
		for a, b := range resp.Approvals {
			fmt.Println(a, b)
		}
	}

	return nil
}

func (c *FabricClient) commitCC(channelId string, ccInfo *ChaincodeInfo, orgResMgmt *resmgmt.Client, peerEndpoints []string, ordererEndpoint string) error {
	req := resmgmt.LifecycleCommitCCRequest{
		Name:              ccInfo.Label,
		Version:           ccInfo.Version,
		Sequence:          ccInfo.Sequence,
		EndorsementPlugin: "escc",
		ValidationPlugin:  "vscc",
		SignaturePolicy:   c.SignaturePolicy,
		InitRequired:      true,
	}
	if _, err := orgResMgmt.LifecycleCommitCC(channelId, req,
		resmgmt.WithRetry(c.RetryOpts),
		resmgmt.WithTargetEndpoints(peerEndpoints...),
		resmgmt.WithOrdererEndpoint(ordererEndpoint)); err != nil {
		return err
	}
	return nil
}

func (c *FabricClient) queryCommittedCC(channelId, label string, orgResMgmt *resmgmt.Client, peerEndpoints []string) ([]resmgmt.LifecycleChaincodeDefinition, error) {
	if resp, err := orgResMgmt.LifecycleQueryCommittedCC(channelId,
		resmgmt.LifecycleQueryCommittedCCRequest{Name: label},
		resmgmt.WithTargetEndpoints(peerEndpoints...),
		resmgmt.WithRetry(c.RetryOpts)); err != nil {
		return nil, err
	} else {
		return resp, nil
	}
}

func (c *FabricClient) InitCC(channelId, label string, peer *PeerInfo) error {
	clientChannelContext := c.Sdk.ChannelContext(channelId, fabsdk.WithUser(peer.OrgInfo.Admin), fabsdk.WithOrg(peer.OrgInfo.Name))
	client, err := channel.New(clientChannelContext)
	if err != nil {
		fmt.Printf("Failed to create new channel client: %s", err)
	}
	if _, err = client.Execute(
		channel.Request{ChaincodeID: label, Fcn: "init", Args: [][]byte{}, IsInit: true},
		channel.WithRetry(c.RetryOpts),
		channel.WithTargetEndpoints(peer.Endpoint)); err != nil {
		return err
	}
	return nil
}

func (c *FabricClient) CreateChannel(channel *ChannelInfo, orderer *OrdererInfo, org *OrganizationInfo) error {
	mspc, err := mspClient.New(c.Sdk.Context(), mspClient.WithOrg(org.Name))
	if err != nil {
		return err
	}
	adminIdentity, err := mspc.GetSigningIdentity(org.Admin)
	if err != nil {
		return err
	}
	req := resmgmt.SaveChannelRequest{
		ChannelID:         channel.ChannelId,
		ChannelConfigPath: channel.ChannelConfigPath,
		SigningIdentities: []mspProvider.SigningIdentity{adminIdentity}}

	ordererContext := c.Sdk.Context(fabsdk.WithUser(orderer.OrgInfo.Admin), fabsdk.WithOrg(orderer.OrgInfo.Name))
	resMgmtClient, err := resmgmt.New(ordererContext)
	if err != nil {
		return err
	}

	if _, err = resMgmtClient.SaveChannel(req,
		resmgmt.WithRetry(c.RetryOpts),
		resmgmt.WithOrdererEndpoint(orderer.Endpoint)); err != nil {
		return err
	}
	fmt.Println(fmt.Sprintf("create channel %s successfully", channel.ChannelId))
	return nil
}

func (c *FabricClient) InstallChaincode(channelId string, peer *PeerInfo, ccInfo *ChaincodeInfo) error {
	var err error

	resMgmt, err := resmgmt.New(c.Sdk.Context(
		fabsdk.WithUser(peer.OrgInfo.Admin),
		fabsdk.WithOrg(peer.OrgInfo.Name)))
	if err != nil {
		fmt.Printf("Failed to create new resource management client: %s\n", err)
		return err
	}

	err = c.installCC(ccInfo, resMgmt, peer.Endpoint)
	if err != nil {
		fmt.Println("1. installCC failed, ", err)
		return err
	} else {
		fmt.Println("1. installCC successfully")
	}

	err = c.getInstalledCCPackage(ccInfo.PackageId, resMgmt, peer.Endpoint)
	if err != nil {
		fmt.Println("2. getInstalledCCPackage failed, ", err)
		return err
	} else {
		fmt.Println("2. getInstalledCCPackage successfully")
	}

	_, err = c.queryInstalled(resMgmt, peer.Endpoint)
	if err != nil {
		fmt.Println("3. queryInstalled failed, ", err)
		return err
	} else {
		fmt.Println("3. queryInstalled successfully")
	}

	err = c.approveCC(channelId, ccInfo, resMgmt, peer.Endpoint, c.Orderer.Endpoint)
	if err != nil {
		fmt.Println("4. approveCC failed, ", err)
		return err
	} else {
		fmt.Println("4. approveCC successfully")
	}

	err = c.queryApprovedCC(channelId, ccInfo, resMgmt, peer.Endpoint)
	if err != nil {
		fmt.Println("5. queryApprovedCC failed, ", err)
		return err
	} else {
		fmt.Println("5. queryApprovedCC successfully")
	}

	err = c.checkCCCommitReadiness(channelId, ccInfo, resMgmt, peer.Endpoint)
	if err != nil {
		fmt.Println("6. checkCCCommitReadiness failed, ", err)
		return err
	} else {
		fmt.Println("6. checkCCCommitReadiness successfully")
	}

	err = c.commitCC(channelId, ccInfo, resMgmt, []string{peer.Endpoint}, c.Orderer.Endpoint)
	if err != nil {
		fmt.Println("7. commitCC failed, ", err)
		return err
	} else {
		fmt.Println("7. commitCC successfully")
	}

	_, err = c.queryCommittedCC(channelId, ccInfo.Label, resMgmt, []string{peer.Endpoint})
	if err != nil {
		fmt.Println("8. queryCommittedCC failed, ", err)
		return err
	} else {
		fmt.Println("8. queryCommittedCC successfully")
	}

	return nil
}

func (c *FabricClient) InstallChaincodeToNew(channelId string, oldPeer *PeerInfo, newPeer *PeerInfo, ccInfo *ChaincodeInfo) error {
	var err error

	oldResMgmt, err := resmgmt.New(c.Sdk.Context(
		fabsdk.WithUser(oldPeer.OrgInfo.Admin),
		fabsdk.WithOrg(oldPeer.OrgInfo.Name)))

	newResMgmt, err := resmgmt.New(c.Sdk.Context(
		fabsdk.WithUser(newPeer.OrgInfo.Admin),
		fabsdk.WithOrg(newPeer.OrgInfo.Name)))

	err = c.installCC(ccInfo, oldResMgmt, oldPeer.Endpoint)
	if err != nil {
		fmt.Println("1. installCC failed, ", err)
		return err
	} else {
		fmt.Println("1. installCC successfully")
	}
	err = c.installCC(ccInfo, newResMgmt, newPeer.Endpoint)
	if err != nil {
		fmt.Println("1. installCC failed, ", err)
		return err
	} else {
		fmt.Println("1. installCC successfully")
	}

	err = c.getInstalledCCPackage(ccInfo.PackageId, oldResMgmt, oldPeer.Endpoint)
	if err != nil {
		fmt.Println("2. getInstalledCCPackage failed, ", err)
		return err
	} else {
		fmt.Println("2. getInstalledCCPackage successfully")
	}
	err = c.getInstalledCCPackage(ccInfo.PackageId, newResMgmt, newPeer.Endpoint)
	if err != nil {
		fmt.Println("2. getInstalledCCPackage failed, ", err)
		return err
	} else {
		fmt.Println("2. getInstalledCCPackage successfully")
	}

	_, err = c.queryInstalled(oldResMgmt, oldPeer.Endpoint)
	if err != nil {
		fmt.Println("3. queryInstalled failed, ", err)
		return err
	} else {
		fmt.Println("3. queryInstalled successfully")
	}
	_, err = c.queryInstalled(newResMgmt, newPeer.Endpoint)
	if err != nil {
		fmt.Println("3. queryInstalled failed, ", err)
		return err
	} else {
		fmt.Println("3. queryInstalled successfully")
	}

	err = c.approveCC(channelId, ccInfo, oldResMgmt, oldPeer.Endpoint, c.Orderer.Endpoint)
	if err != nil {
		fmt.Println("4. approveCC failed, ", err)
		return err
	} else {
		fmt.Println("4. approveCC successfully")
	}
	err = c.approveCC(channelId, ccInfo, newResMgmt, newPeer.Endpoint, c.Orderer.Endpoint)
	if err != nil {
		fmt.Println("4. approveCC failed, ", err)
		return err
	} else {
		fmt.Println("4. approveCC successfully")
	}

	err = c.queryApprovedCC(channelId, ccInfo, oldResMgmt, oldPeer.Endpoint)
	if err != nil {
		fmt.Println("5. queryApprovedCC failed, ", err)
		return err
	} else {
		fmt.Println("5. queryApprovedCC successfully")
	}
	err = c.queryApprovedCC(channelId, ccInfo, newResMgmt, newPeer.Endpoint)
	if err != nil {
		fmt.Println("5. queryApprovedCC failed, ", err)
		return err
	} else {
		fmt.Println("5. queryApprovedCC successfully")
	}

	err = c.checkCCCommitReadiness(channelId, ccInfo, oldResMgmt, oldPeer.Endpoint)
	if err != nil {
		fmt.Println("6. checkCCCommitReadiness failed, ", err)
		return err
	} else {
		fmt.Println("6. checkCCCommitReadiness successfully")
	}
	err = c.checkCCCommitReadiness(channelId, ccInfo, newResMgmt, newPeer.Endpoint)
	if err != nil {
		fmt.Println("6. checkCCCommitReadiness failed, ", err)
		return err
	} else {
		fmt.Println("6. checkCCCommitReadiness successfully")
	}

	err = c.commitCC(channelId, ccInfo, oldResMgmt, []string{oldPeer.Endpoint, newPeer.Endpoint}, c.Orderer.Endpoint)
	if err != nil {
		fmt.Println("7. commitCC failed, ", err)
		return err
	} else {
		fmt.Println("7. commitCC successfully")
	}

	_, err = c.queryCommittedCC(channelId, ccInfo.Label, oldResMgmt, []string{oldPeer.Endpoint})
	if err != nil {
		fmt.Println("8. queryCommittedCC failed, ", err)
		return err
	} else {
		fmt.Println("8. queryCommittedCC successfully")
	}
	_, err = c.queryCommittedCC(channelId, ccInfo.Label, newResMgmt, []string{newPeer.Endpoint})
	if err != nil {
		fmt.Println("8. queryCommittedCC failed, ", err)
		return err
	} else {
		fmt.Println("8. queryCommittedCC successfully")
	}

	return nil
}

func (c *FabricClient) Query(channelId, user, org, peer string) {
	// fabricClient.Query("User1", "org1", "peer0.org1.example.com")
	ccContext := c.Sdk.ChannelContext(channelId, fabsdk.WithUser(user), fabsdk.WithOrg(org))
	cc, err := channel.New(ccContext)
	if err != nil {
		fmt.Printf("Failed to create new resource management client: %s\n", err)
	}
	args := [][]byte{[]byte("10"), []byte(""), []byte(""), []byte(""), []byte("userName"), []byte("zze"), []byte("")}
	resp, err := cc.Execute(channel.Request{
		ChaincodeID: "exp02",
		Fcn:         "query",
		Args:        args,
	},
		channel.WithRetry(c.RetryOpts),
		channel.WithTargetEndpoints(peer),
	)
	if err != nil {
		fmt.Printf("Failed to query funds: %s\n\n", err)
	}
	fmt.Println(string(resp.Payload))
}
